﻿// ———————————————————————–
// <copyright file="AWS_RouterRulesDAL.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// AWS_RouterRulesDAL.cs - Data Access Layer For the Rules to Be Used To Route EDXL-DE Messages
// Project: EDXLSharp_AWSRouter - ICNETDAL
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2013
// Author:      Brian Wilkins The MITRE Corporation
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using EDXLSharp;
using ICNETServices;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.SQS;
using Amazon.SQS.Model;
using Amazon.Util;
using ICNETSerializers;
using System.Xml.Serialization;
using System.Xml;
using System.IO;

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

namespace ICNETDAL
{
  /// <summary>
  /// This class contains methods that are used to retrieve and modify the rules that route EDXL-DE messages. 
  /// </summary>
  public static class AWSRouterRulesDAL
  {
    #region Public Member Functions
    private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Returns the endpoints that all messages get federated to.
    /// </summary>
    /// <returns>Router Rules</returns>
    public static RouterRules GetFederateAllMessagesEndpoints()
    {
      return GetRouterRules("*", "*");
    }

    /// <summary>
    /// Returns all Router Rules(serializer).
    /// </summary>
    /// <returns>Router Rules</returns>
    public static RouterRules GetAllRouterRules()
    {
      RouterRules routerRules = new RouterRules();

      ScanResult result = null;
      try
      {
        Dictionary<string, AttributeValue> lastEvaluatedKey = null;

        do
        {
          ScanRequest req = new ScanRequest();
          req.TableName = AWSConstants.RulesProcessing.DynamoInfo.TableName;

          if (lastEvaluatedKey != null)
          {
            req.ExclusiveStartKey = lastEvaluatedKey;
          }

          result = AWSUtils.DynamoClient.Scan(req);

          lastEvaluatedKey = result.LastEvaluatedKey;

          if (!LoadRouterRulesFromDBItems(routerRules, result.Items))
          {
            // We errored out an should return null
            return null;
          }


        } while (lastEvaluatedKey != null && lastEvaluatedKey.Count != 0);


        return routerRules;
      }
      catch (Exception e)
      {
        Log.Error("Error scanning from dynamo Rules table", e);
        return null;
      }
    }

    /// <summary>
    /// Takes a ValueList urn and value and looks up the corresponding Router Rules(serializer).
    /// If the valuelist value is empty, null, or whitespace, all rules for the ValueList urn will be returned.
    /// </summary>
    /// <param name="ValListUrn">ValueList URN</param>
    /// <param name="ValListVal">ValueList Value - optional</param>
    /// <returns>Router Rules</returns>
    public static RouterRules GetOneSetOfRouterRules(string ValListUrn, string ValListVal)
    {
      RouterRules routerRules = new RouterRules();

      QueryResponse response = null;
      try
      {
        Dictionary<string, AttributeValue> lastEvaluatedKey = null;

        do
        {
          QueryRequest req = new QueryRequest();
          req.TableName = AWSConstants.RulesProcessing.DynamoInfo.TableName;

          Dictionary<string, Condition> keyConditions = new Dictionary<string, Condition>();
          Condition cond = new Condition();
          List<AttributeValue> attvalList = new List<AttributeValue>();

          //add URN as a condition
          AttributeValue attval = new AttributeValue();
          attval.S = ValListUrn;
          attvalList.Add(attval);

          cond.AttributeValueList = attvalList;
          cond.ComparisonOperator = "EQ";
          keyConditions.Add("valueListUrn", cond);

          //set a filter if we need one
          if (!string.IsNullOrWhiteSpace(ValListVal))
          {
            //add Value as a condition
            attval = new AttributeValue();
            attval.S = ValListVal;
            attvalList.Add(attval);

            cond.AttributeValueList = attvalList;
            cond.ComparisonOperator = "EQ";
            keyConditions.Add("value", cond);
          }
          req.KeyConditions = keyConditions;

          if (lastEvaluatedKey != null)
          {
            req.ExclusiveStartKey = lastEvaluatedKey;
          }

          response = AWSUtils.DynamoClient.Query(req);

          lastEvaluatedKey = response.LastEvaluatedKey;

          if (!LoadRouterRulesFromDBItems(routerRules, response.Items))
          {
            // We errored out an should return null
            return null;
          }

        } while (lastEvaluatedKey != null && lastEvaluatedKey.Count != 0);


        return routerRules;
      }
      catch (Exception e)
      {
        Log.Error("Error querying from Dynamo Rules table", e);
        return null;
      }
    }
    /// <summary>
    /// Takes a ValueList urn and value and looks up the corresponding router rule(serializer).
    /// If the valuelist urn is empty, null, or whitespace, all rules will be returned
    /// If the valuelist value is empty, null, or whitespace, all rules for the ValueList urn will be returned.
    /// </summary>
    /// <param name="ValListUrn">ValueList URN - optional</param>
    /// <param name="ValListVal">ValueList Value - optional</param>
    /// <returns>the router rule</returns>
    public static RouterRules GetRouterRules(string ValListUrn, string ValListVal)
    {
      if (!string.IsNullOrWhiteSpace(ValListUrn))
      {
        return GetOneSetOfRouterRules(ValListUrn, ValListVal);
      }
      else 
      {
        return GetAllRouterRules();
      }     
    }

    /// <summary>
    /// Takes a ValueList urn and value and looks up the corresponding router rule(serializer).
    /// If the valuelist urn is empty, null, or whitespace, all rules will be returned
    /// If the valuelist value is empty, null, or whitespace, all rules for the ValueList urn will be returned.
    /// </summary>
    /// <param name="ValListUrn">ValueList URN - optional</param>
    /// <param name="ValListVal">ValueList Value - optional</param>
    /// <returns>the router rule xml</returns>
    public static XElement GetRouterRulesXML(string ValListUrn, string ValListVal)
    {
      return RouterRulesHelper.ToXElement(GetRouterRules(ValListUrn, ValListVal));
    }

    public static XElement UpdateRouterRule(bool RemoveAttribute, RouterRules TheRouterRules)
    {
      XElement retVal = new XElement("Response");

      if (TheRouterRules == null || TheRouterRules.Rules.Count == 0)
      {
        retVal.Add(new XElement("Status", "Error"));
        retVal.Add(new XElement("Info", "Missing Rules"));
        return retVal;
      }
      
      
      foreach (RouterRule aRule in TheRouterRules.Rules)
      {
        //TODO:deal with this error better
        if (string.IsNullOrWhiteSpace(aRule.ValueListUrn) || string.IsNullOrWhiteSpace(aRule.Value))
        {
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Missing ValueListUrn or Value: " + aRule.ValueListUrn + ", " + aRule.Value));
        }
        else if (!aRule.FederationURIsSpecified && !aRule.FeedHashesSpecified)
        {
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Missing FeedHash or FederationUri for: " + aRule.ValueListUrn + ", " + aRule.Value));
        }
        else
        {
          UpdateItemRequest req = new UpdateItemRequest()
          {
            TableName = AWSConstants.RulesProcessing.DynamoInfo.TableName
          };

          //add keys
          Dictionary<string, AttributeValue> keys = new Dictionary<string, AttributeValue>();
          keys.Add("valueListUrn", new AttributeValue { S = aRule.ValueListUrn });
          keys.Add("value", new AttributeValue { S = aRule.Value });
          req.Key = keys;

          //prepare for using expression names
          req.ExpressionAttributeNames = new Dictionary<string, string>();
          req.ExpressionAttributeValues = new Dictionary<string, AttributeValue>();

          //set up expression for _ValueListType expression if we are adding
          if (!RemoveAttribute)
          {
            req.ExpressionAttributeNames.Add("#Type", "_ValueListType");
            req.ExpressionAttributeValues.Add(":type", new AttributeValue { S = aRule.Kind.ToString() });
            req.UpdateExpression = "SET #Type = :type";
          }

          List<string> stringSetActions = new List<string>();

          //set up action for _FeedHash
          if (aRule.FeedHashesSpecified)
          {
            req.ExpressionAttributeNames.Add("#FeedHash", "_FeedHash");
            var feedHashes = aRule.FeedHashes.Select(fh => DEDalUtils.CreateValueListHash(fh.ValueListUrn, fh.Value));
            req.ExpressionAttributeValues.Add(":feedhash", new AttributeValue { SS = feedHashes.ToList<string>() });

            stringSetActions.Add("#FeedHash :feedhash");
          }

          //set up action for _FederationUris
          if (aRule.FederationURIsSpecified)
          {
            req.ExpressionAttributeNames.Add("#FedUri", "_FederationUri");
            req.ExpressionAttributeValues.Add(":feduri", new AttributeValue { SS = aRule.FederationURIs });

            stringSetActions.Add("#FedUri :feduri");
          }

          //add to the update expression for the string sets if there were actions.
          if (stringSetActions.Count > 0)
          {
            if (!RemoveAttribute)
            {
              req.UpdateExpression += " ADD " + String.Join(", ", stringSetActions);
            }
            else
            {
              req.UpdateExpression += " DELETE " + String.Join(", ", stringSetActions);
            }
          }

          //if there actually is an update to make actually do it.
          if (!string.IsNullOrWhiteSpace(req.UpdateExpression))
          {
            UpdateItemResponse response = AWSUtils.DynamoClient.UpdateItem(req);

            if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
            {
              Log.Info("Router Rules Updated in Dynamo");
              retVal.Add(new XElement("Status", "Success"));
              retVal.Add(new XElement("Info", ""));
            }
            else
            {
              Log.Error("Dynamo Error" + response.HttpStatusCode.ToString());
              retVal.Add(new XElement("Status", "Error"));
              retVal.Add(new XElement("Info", "Dynamo Error: " + response.HttpStatusCode.ToString()));
            }
          }

        }
      }
      return retVal;
    }

    /// <summary>
    /// Creates the routing rules to the RouterRules table
    /// </summary>
    /// <param name="TheRouterRules">The routing rules that should be added to the database</param>
    /// <returns> IF the inputted rule was properly formatted, it will be returned as an XML object with a new GUID
    /// If the inputted rule is improperly formatted, then an XML object will be returned, but not added to the database</returns>
    public static XElement AddRouterRules(RouterRules TheRouterRules)
    {
      XElement retVal = new XElement("Response");

      List<Dictionary<string, AttributeValue>> putItems = new List<Dictionary<string,AttributeValue>>();

      //make sure the rules wrapper and rules list aren't null
      if (TheRouterRules != null && TheRouterRules.Rules != null )
      {
        if (TheRouterRules.Rules.Count >= 1)
        {
          putItems = LoadRouterRulesToDBItems(TheRouterRules);
        }
        else //no rules
        {
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Missing Rules"));
          return retVal;
        }
      }
      else
      {
        retVal.Add(new XElement("Status", "Error"));
        retVal.Add(new XElement("Info", "Missing Rules"));
        return retVal;
      }

      if (putItems.Count == 1) //do a single write
      {
        PutItemRequest putRequest = new PutItemRequest();
        putRequest.TableName = AWSConstants.RulesProcessing.DynamoInfo.TableName;

        try
        {
          PutItemResponse putResponse = AWSUtils.DynamoClient.PutItem(putRequest);
          if (putResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
          {
            Log.Info("Router Rule Added to Dynamo");
            retVal.Add(new XElement("Status", "Success"));
            retVal.Add(new XElement("Info", RouterRulesHelper.ToString(TheRouterRules)));
          }
          else
          {
            Log.Error("Dynamo Error" + putResponse.HttpStatusCode.ToString());
            retVal.Add(new XElement("Status", "Error"));
            retVal.Add(new XElement("Info", "Dynamo Error: " + putResponse.HttpStatusCode.ToString()));
          }
        }
        catch (Exception e)
        {
          Log.Error("Error writing Router Rule to dynamo", e);
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Error writing Router Rule to dynamo: " + e.ToString()));
        }
      }
      else if (putItems.Count > 1) //do a batch write
      {
        BatchWriteItemRequest batchRequest = new BatchWriteItemRequest();

        Dictionary<string, List<WriteRequest>> requestItems = new Dictionary<string, List<WriteRequest>>();
        List<WriteRequest> writeRequests = new List<WriteRequest>();

        foreach (Dictionary<string, AttributeValue> item in putItems)
        {
          writeRequests.Add(new WriteRequest(new PutRequest(item)));
        }
        requestItems.Add(AWSConstants.RulesProcessing.DynamoInfo.TableName, writeRequests);
        batchRequest.RequestItems = requestItems;
        try
        {
          BatchWriteItemResponse batchResponse = AWSUtils.DynamoClient.BatchWriteItem(batchRequest);
          if (batchResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
          {
            Log.Info(putItems.Count.ToString() + " Router Rules Added to Dynamo");
            retVal.Add(new XElement("Status", "Success"));
            retVal.Add(new XElement("Info", putItems.Count.ToString() + " Router Rules Added"));
          }
          else
          {
            Log.Error("Dynamo Error" + batchResponse.HttpStatusCode.ToString());
            retVal.Add(new XElement("Status", "Error"));
            retVal.Add(new XElement("Info", "Dynamo Error: " + batchResponse.HttpStatusCode.ToString()));
          }
        }
        catch (Exception e)
        {
          Log.Error("Error writing Router Rule to dynamo", e);
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Error writing Router Rule to dynamo: " + e.ToString()));
        }
      }
      
      return retVal;
    }

    /// <summary>
    /// Deletes a routing rule from the RouterRules Database
    /// </summary>
    /// <param name="ValListUrn">ValueList Urn of the routing rule that should be deleted</param>
    /// <param name="ValListValue">ValueList VAlue of the routing rule that should be deleted - optional</param>
    public static XElement DeleteRouterRules(string ValListUrn, string ValListValue)
    {
      XElement retVal = new XElement("Response");

      DeleteItemRequest deleteRequest = new DeleteItemRequest();
      deleteRequest.TableName = AWSConstants.RulesProcessing.DynamoInfo.TableName;

      Dictionary<string, AttributeValue> deleteKeys = new Dictionary<string, AttributeValue>();
      AttributeValue value;

      if (!string.IsNullOrWhiteSpace(ValListUrn) && !string.IsNullOrWhiteSpace(ValListValue))
      {
        value = new AttributeValue();
        value.S = ValListUrn;
        deleteKeys.Add(AWSConstants.RulesProcessing.DynamoInfo.HashName, value);

        value = new AttributeValue();
        value.S = ValListValue;
        deleteKeys.Add(AWSConstants.RulesProcessing.DynamoInfo.RangeName, value);
      }
      else if (!string.IsNullOrWhiteSpace(ValListUrn))
      {
        //TODO: queury to get all items for the URN, then delete them based on hash + range
        //value = new AttributeValue();
        //value.S = ValListUrn;
        //deleteKeys.Add(AWSConstants.RulesProcessing.DynamoInfo.HashName, value);
      }

      try
      {
        DeleteItemResponse deleteResponse = AWSUtils.DynamoClient.DeleteItem(deleteRequest);
        if (deleteResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
        {
          Log.Info("Deleteing Router Rule");
          retVal.Add(new XElement("Status", "Success"));
          retVal.Add(new XElement("Info", "Urn = " + ValListUrn + ", Value = " + ValListValue));
        }
        else
        {
          Log.Error("Dynamo Error" + deleteResponse.HttpStatusCode.ToString());
          retVal.Add(new XElement("Status", "Error"));
          retVal.Add(new XElement("Info", "Dynamo Error: " + deleteResponse.HttpStatusCode.ToString()));
        }
      }
      catch (Exception e)
      {
        Log.Error("Error deleting Router Rule to dynamo", e);
        retVal.Add(new XElement("Status", "Error"));
        retVal.Add(new XElement("Info", "Error deleting Router Rule in dynamo: " + e.ToString()));
      }

      return retVal;
    }

    #endregion
    
    #region Private Member Functions

    /// <summary>
    /// Loads information from a set of RouterRules table rows
    /// </summary>
    /// <param name="routerRules">The router rules item.</param>
    /// <param name="Items">RouterRules table rows</param>
    /// <returns>RouterRules or null if it failed.</returns>
    private static bool LoadRouterRulesFromDBItems(RouterRules routerRules, List<Dictionary<string, AttributeValue>> Items)
    {


      foreach (Dictionary<string, AttributeValue> item in Items)
      {
        RouterRule aRule = new RouterRule();

        AttributeValue ruleValue = item["valueListUrn"];
        aRule.ValueListUrn = ruleValue.S;

        ruleValue = item["value"];
        aRule.Value = ruleValue.S;

        ruleValue = item["_ValueListType"];
        string sKind = ruleValue.S;
        ValueListSource source;
        try
        {
          source = (ValueListSource)Enum.Parse(typeof(ValueListSource), sKind, true);
          aRule.Kind = source;
        }
        catch (Exception e)
        {
          Log.Error("Error converting ValueList kind to Enumeration, Returned value: " + sKind, e);
          return false;
        }

        List<string> hashes;
        // String sets cannot be empty so we need to check if the attribute exists first
        if (item.ContainsKey("_FeedHash"))
        {
          hashes = item["_FeedHash"].SS;
        }
        else
        {
          // since the attribute was not found we should use an empty set
          hashes = new List<string>();
        }

        if (hashes != null && hashes.Count > 0)
        {
          List<FeedHash> feedHashes = new List<FeedHash>();

          foreach (string feedHash in hashes)
          {
            FeedHash hash = new FeedHash();

            string[] feedHashSplit = feedHash.Split(new string[] { "::" }, StringSplitOptions.None);

            if (feedHashSplit.Length != 2)
            {
              Log.Error("Invalid FeedHash returned from Rules table. Expecting :: delimited string. Value returned is: " + feedHash);
              return false;
            }

            hash.ValueListUrn = feedHashSplit[0];
            hash.Value = feedHashSplit[1];

            feedHashes.Add(hash);
          }
          aRule.FeedHashes = feedHashes;
        }

        List<string> uris;
        // String sets cannot be empty so we need to check if the attribute exists first
        if (item.ContainsKey("_FederationUri"))
        {
          uris = item["_FederationUri"].SS;
        }
        else
        {
          // since the attribute was not found we should use an empty set
          uris = new List<string>();
        }
        aRule.FederationURIs = uris;


        routerRules.Rules.Add(aRule);
      }
      return true;
    }



    private static List<Dictionary<string, AttributeValue>> LoadRouterRulesToDBItems(RouterRules TheRouterRules)
    {
      List<Dictionary<string, AttributeValue>> items = new List<Dictionary<string, AttributeValue>>();

      foreach (RouterRule aRule in TheRouterRules.Rules)
      {
        Dictionary<string, AttributeValue> item = new Dictionary<string, AttributeValue>();

        //add valuelist urn
        AttributeValue dynamovalue = new AttributeValue();
        dynamovalue.S = aRule.ValueListUrn;
        item.Add(AWSConstants.RulesProcessing.DynamoInfo.HashName, dynamovalue);

        //add valuelist value
        dynamovalue = new AttributeValue();
        dynamovalue.S = aRule.Value;
        item.Add(AWSConstants.RulesProcessing.DynamoInfo.RangeName, dynamovalue);

        //add valuelist kind
        dynamovalue = new AttributeValue();
        dynamovalue.S = aRule.Kind.ToString();
        item.Add("_ValueListType", dynamovalue);

        //add feed hashes
        List<string> stringSet = new List<string>();

        foreach (FeedHash aFeedHash in aRule.FeedHashes)
        {
          stringSet.Add(DEDalUtils.CreateValueListHash(aFeedHash.ValueListUrn, aFeedHash.Value));
        }

        if (stringSet.Count > 0)
        {
          dynamovalue = new AttributeValue();
          dynamovalue.SS = stringSet;
          item.Add("_FeedHash", dynamovalue);
        }

        //add federation uri
        if (aRule.FederationURIs != null && aRule.FederationURIs.Count > 0)
        {
          dynamovalue = new AttributeValue();
          //need to sanitize the URIs
          dynamovalue.SS = aRule.FederationURIs;
          item.Add("_FederationUri", dynamovalue);
        }

        items.Add(item);
      }

      return items;
    }

    #endregion         
  }
}
